using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Xml.Serialization;

namespace SpellCenter.Core
{
  /// <summary>
  /// The packing list stores all the files that have been indexed
  /// and when the file was last modified
  /// This is the concrete list of what is in the index
  /// </summary>
  sealed class PackingList : ICollection<string>
  {
    // these lists must stay in sync
    private readonly Collection<string> _filenames;
    private readonly Collection<DateTime> _timestamps;

    public const string DEFAULT_FILENAME = "packinglist";

    public PackingList()
    {
      _filenames = new Collection<string>();
      _timestamps = new Collection<DateTime>();
    }

    public PackingList(IEnumerable<string> files): this()
    {
      Add(files);
    }

    /// <summary>
    /// If filename exists, returns the Packinglist deserialized
    /// otherwise creates a new PackingList
    /// </summary>
    /// <param name="filename"></param>
    /// <returns></returns>
    static public PackingList CreateFromFile(string filename)
    {
      PackingList packingList = new PackingList();

      if (File.Exists(filename))
      {
        XmlSerializer packingListSerializer = new XmlSerializer(typeof(Collection<PackingInfo>));
        using (FileStream stream = File.OpenRead(filename))
        {
          Collection<PackingInfo> files = (Collection<PackingInfo>) packingListSerializer.Deserialize(stream);
          foreach (PackingInfo file in files)
          {
            if (packingList.Contains(file.Name))
            {
              throw new ArgumentOutOfRangeException("filename", "filename already exists in collection");
            }
            packingList.Add(file.Name, file.Timestamp);
          }

        }
      }
      return packingList;
    }

    public void SaveAs(string filename)
    {
      Collection<PackingInfo> files = new Collection<PackingInfo>();
      for(int i = 0; i != _filenames.Count;++i)
      {
        PackingInfo packingInfo = new PackingInfo();
        packingInfo.Name = _filenames[i];
        packingInfo.Timestamp = _timestamps[i];
        files.Add(packingInfo);
      }
      XmlSerializer packingListSerializer = new XmlSerializer(typeof(Collection<PackingInfo>));
      //File.Create creates or overwrites
      using (Stream stream = File.Create(filename))
      {
        packingListSerializer.Serialize(stream, files);
      }
    }

    public void Add(string filename)
    {
      if (Contains(filename))
      {
        throw new ArgumentOutOfRangeException("filename", "filename already exists in collection");
      }
      Add(filename, File.GetLastWriteTimeUtc(filename));
    }

    public void Add(IEnumerable<string> filenames)
    {
      foreach (string filename in filenames)
      {
        Add(filename);
      }
    }

    private void Add(string filename, DateTime dateTime)
    {
      _timestamps.Add(dateTime);
      _filenames.Add(filename);
    }

    public void Clear()
    {
      _filenames.Clear();
      _timestamps.Clear();
    }

    public bool Contains(string filename)
    {
      return _filenames.Contains(filename);
    }

    public void CopyTo(string[] array, int arrayIndex)
    {
      _filenames.CopyTo(array, arrayIndex);
    }

    public bool Remove(string filename)
    {
      int index = _filenames.IndexOf(filename);
      if (index == -1) //not found
      {
        return false;
      }
      _timestamps.RemoveAt(index);
      _filenames.RemoveAt(index);
      return true;
    }

    public int Count
    {
      get { return this._filenames.Count; }
    }

    #region ICollection<string> Members

    public bool IsReadOnly
    {
      get { return false; }
    }

    #endregion

    public DateTime GetTimestamp(string filename)
    {
      int index = IndexOf(filename);
      return _timestamps[index];
    }

    private int IndexOf(string filename)
    {
      int index = this._filenames.IndexOf(filename);
      if(index == -1) // not found
      {
        throw new ArgumentOutOfRangeException("filename", "filename must be contained in collection");
      }
      return index;
    }

    public void UpdateTimestamp(string file)
    {
      int index = IndexOf(file);
      _timestamps[index] = File.GetLastWriteTimeUtc(file);
    }

    /// <summary>
    /// A file is not contained if it does not exist in this PackingList 
    /// but is in the given set of files
    /// </summary>
    /// <param name="fileList">A set of files to filter</param>
    /// <returns>The set of files that are not in the PackingList but are in the given files</returns>
    public IEnumerable<string> FilterToNotContained(IEnumerable<string> fileList)
    {
      foreach (string file in fileList)
      {
        if (!Contains(file))
        {
          yield return file;
        }
      }
    }

    /// <summary>
    /// A file is contained if it exists in this PackingList and the given
    /// set of files
    /// </summary>
    /// <param name="fileList">A set of files to filter</param>
    /// <returns>The set of files that exist in both the PackingList and the given files</returns>
    public IEnumerable<string> FilterToContained(IEnumerable<string> fileList)
    {
      foreach (string file in fileList)
      {
        if (Contains(file))
        {
          yield return file;
        }
      }
    }

    /// <summary>
    /// A file is modified if it exists in both PackingLists but
    /// has different timestamps
    /// </summary>
    /// <param name="packingList">The packing list to be filtered against this</param>
    /// <returns>The filenames of files that have been modified</returns>
    public IEnumerable<string> FilterToModified(PackingList packingList)
    {
      foreach (string file in FilterToContained(packingList))
      {
        if (packingList.GetTimestamp(file) != GetTimestamp(file))
        {
          yield return file;
        }
      }
    }

    public static void Compare(PackingList newPackingList, 
                               PackingList oldPackingList, 
                               out IEnumerable<string> filesToAddToOld, 
                               out IEnumerable<string> filesToRemoveFromOld, 
                               out IEnumerable<string> filesToUpdateInOld)
    {
      // if a file is in old and is not in new: remove
      filesToRemoveFromOld =
        newPackingList.FilterToNotContained(oldPackingList);

      // if a file is in new and is not in old: add
      filesToAddToOld =
        oldPackingList.FilterToNotContained(newPackingList);

      // if a file is in both and has been modified (different timestamps): update
      filesToUpdateInOld =
        oldPackingList.FilterToModified(newPackingList);
    }

    #region IEnumerable<string> Members

    IEnumerator<string> IEnumerable<string>.GetEnumerator()
    {
      return _filenames.GetEnumerator();
    }

    #endregion

    #region IEnumerable Members

    public IEnumerator GetEnumerator()
    {
      return _filenames.GetEnumerator();
    }

    #endregion

  }
}
